/*jslint
    nomen: false,
    debug: true,
    indent: 3,
    plusplus: false,
    evil: false, 
    onevar: false,
    browser: true,
    white: false
*/
/*global
    window: true,
    navigator: true,
    XMLHttpRequest: true,
    ActiveXObject: true,
    unescape: true
*/

if(typeof (Object.create) !== "function")  {
   Object.create = function(o)   {
      function F()   {};
      F.prototype = o;
      return new F();
   };
}

/**
 * Top Level jaf function and a namespace
 * jaf is the global namespace for the framework. Other projects can create
 * global namespaces. Although its not advisable to create a lot of global
 * object, there should be a clear separation between framework namespaces
 * and application specific namespaces. Its advisable to create only one namespace
 * for the entire application
 * @namespace
 * @param {Object} j The jaf object if it already exists, is assigned to jaf
 */
var jaf = function Jaf(j) {
   /**
    * Creates a named Object with a <tt>name</tt> field which is assigned the
    * <tt>strName</tt> and a toString method
    * @private
    * @param {String} strName The name of the named object
    */
   function namedObject(strName)  {
      return {
         name: strName,
         toString: function() {
            return this.name;
         }
      };
   }

   /**
    * Determines the host environment in which the framework is running
    * @private
    * @return The host env object
    */
   function hostEnv()   {
      /**
       * Determines if the userAgent has the specified String
       */
      function isUa(uaString)  {
         if(typeof(navigator) !== "undefined")  {
            return (navigator.userAgent.indexOf(uaString) !== -1);
         }
         return null;
      }

      var he = {};

      he.isGecko = isUa("Gecko");
      he.isIE = isUa("MSIE");
      he.isOpera = typeof(window.opera) !== "undefined";
      he.isWebkit = isUa("AppleWebKit");

      /** @property */
      he.userAgent = function(h)   {
         if(h.isWebkit) {return "AppleWebKit";}
         if(h.isOpera)  {return "Opera";}
         if(h.isGecko) {return "Gecko";}
         if(h.isIE) {return "MSIE";}
         return null;
      }(he);

      return he;
   }

   if(typeof(j) === "undefined")   {
      /** @scope jaf */
      return {
         /**
          * Createa new namespace(s) globally or returns existing ones if already created. Namespaces
          * can be used to avoid creating a lot of global objects and structuring a project's
          * modules and classes. Namespaces are like packages in java. The namespace is a
          * simple string or a dot separated string of characters that are allowed in identifiers
          * e.g. "jaf.core" is a valid namespace but "jaf.1" is not.
          * @param {String} strNs The namespace string
          * @return {Object} The namespace object
          */
         namespace: function(strNs) {
            var arrNsc = strNs.split(".");
            var nsObj = null;
            var i = 0;
            var len = arrNsc.length;

            var nsName = "";

            if(arrNsc[0] === "jaf")  {
               nsObj = jaf;
               i = 1;
               nsName = "jaf.";
            }

            for(; i < len; i++)  {
               var ns = arrNsc[i];
               nsName += (ns + ".");

               if(!nsObj) {
                  if(!window[ns])   {
                     nsObj = window[ns] = namedObject(nsName.substring(0, nsName.length - 1));
                  }else {
                     nsObj = window[ns];
                  }
               }else {
                  if(!nsObj[ns])   {
                     nsObj = nsObj[ns] = namedObject(nsName.substring(0, nsName.length - 1));
                  }else {
                     nsObj = nsObj[ns];
                  }
               }
            }

            return nsObj;
         },

         /**
          * Changes the scope of function "fn" to the "scope" parameter specified or
          * if not, defaults to window scope. The scope of the function determines what
          * "this" inside "fn" evaluates to, inside the function "fn". Any additional arguments
          * specified in this are passed to the underlying "curried" function. If the underlying
          * function is already passed some arguments, the optional arguments are appended
          * to the argument array of the underlying function. To explain this lets take
          * the example below:
          *
          * <pre>
          *    function Person(firstName, lastName)   {
          *       var fname = firstName;
          *       var lname = lastName;
          *
          *       return {
          *          getName: function(separator)  {
          *             return this.getFirstName() + separator + this.getLastName();
          *          },
          *
          *          getFirstName: function()   {
          *             return fname;
          *          },
          *
          *          getLastName: function() {
          *             return lname
          *          }
          *       }
          *    }
          * </pre>
          *
          * But if you call this function on the click of a button like this:
          * &lt;button id="mybutt"&gt;Full Name&lt;/button&gt;
          * <pre>
          *    var btn = document.getElementById("mybutt");
          *    btn.onclick = person.getName;
          * </pre>
          * 1. The this reference changes to the button instance since it was
          * attached as the onclick event handler for this button. In this case
          * the method getName fails with error.
          *
          * To avoid this and preserve the "this" reference inside getName to
          * the person instance we will "curry" this function like so:
          * <pre>
          *    var btn = document.getElementById("mybutt");
          *    btn.onclick = $curry(person.getName, person);
          * </pre>
          *
          * But how would you pass the "separator" argument? Like this:
          * <pre>
          *    var btn = document.getElementById("mybutt");
          *    btn.onclick = $curry(person.getName, person, ", ");
          * </pre>
          *
          * You can pass any number of arguments that are passed to the underlying (curried)
          * function
          * @param {Function} fn The function to curry
          * @param {Object} scope The scope to be set inside the curried function, if
          * not specified, defaults to window
          * @param arguments {...} Any other optional arguments ot be passed to the curried function
          * @example
          * Consider the following "class" Person. The getName method refers to other instance
          * methods of the same class. Hence the "this" keyword inside this function refers
          * to the Person instance.
          *
          */
         curry: function(fn, scope /*, arguments */) {
            scope = scope || window;
            var actualArgs = arguments;

            return function() {
               var args = [];
               for(var j = 0; j < arguments.length; j++) {
                  args.push(arguments[j]);
               }

               for(var i = 2; i < actualArgs.length; i++) {
                  args.push(actualArgs[i]);
               }

               return fn.apply(scope, args);
            };
         },

         /**
          * This method keeps polling for a condition to become true every
          * <tt>numTimeout</tt> intervals. If the conditions becomes true, the
          * <tt>fnCallback</tt> function is called and the polling ends. This method
          * does not block the calling code.
          * @param {Function} funcCondition The condition function that returns true when a certain
          * condition is satisfied
          * @param {long} numTimeout The timeout in milliseconds to wait for polling
          * @param {Function} fnCallback The callback function that is called if
          * the conditions evaluates to true
          */
         poll: function(funcCondition, numTimeout, fnCallback) {
            if(! funcCondition())  {
               window.setTimeout(function()  {
                  jaf.poll(funcCondition, numTimeout, fnCallback);
               }, numTimeout);
            }else {
               window.setTimeout(function()   {
                  fnCallback();
               }, 0);
            }
         },

         /**
          * Defines the host environment for jaf. This tell what browser we are
          * running in. This is specified by the property hostenv.userAgent
          * @property
          */
         hostenv: hostEnv(),

         toString: function() {
            return "jaf";
         }
      };
   }else {
      return j;
   }
}(window.jaf);


// create the jaf.core namespace
jaf.namespace("jaf.core");

/* -------------------------------------------------------------------------- */

/**
 * @class
 * This class contains utility functions required throughout the framework
 */
jaf.core.Util = function()   {
   /**
    * Default ajax options
    */
   var ajaxDefaults = {
      method: "GET",
      url: window.location.href,
      contentType: "application/x-www-form-urlencoded",
      async: true,
      data: null,
      username: null,
      password: null,
      timeout: -1,
      headers: {}
   };

   // The xhtml namespace URI
   var XHTMLNS = "http://www.w3.org/1999/xhtml";

   // Elements that are empty and have no content (for serializing)
   var emptyHtmlElements = [
      "br",
      "hr",
      "img",
      "link"
   ];

   function getNamespaceURI(domElem)   {
      if(domElem.namespaceURI)   {
         return domElem.namespaceURI;
      }else {
         // for IE
         var tagUrn = domElem.tagUrn || "";
         return tagUrn.length > 0 ? tagUrn : XHTMLNS;
      }
   }

   /**
    * Serializes the specified DOM element to String
    * @param domElem The document or a DOM element
    * @param bDeep if true will recursively serialize the children of this element
    * @return the serialized form of the DOM element. i.e. String
    */
   function _asXmlString(domElem, bDeep)   {
      if(! domElem)  {return null;}
      var nodeType = domElem.nodeType;

      if(nodeType !== 9 && nodeType !== 1)  {
         throw new Error("Only Document and Element node supported");
      }

      if(nodeType === 9)  {
         domElem = domElem.documentElement;
      }

      var tagName = domElem.tagName;
      var attrs = domElem.attributes || [];
      var nsURI = getNamespaceURI(domElem);
      var xmlString = "";

      xmlString = "<" + tagName;
      // append the attributes
      var i, len;
      for(i = 0, len = attrs.length; i < len; i++)   {
         var attr = attrs[i];
         var attName = attr.nodeName;
         var nodeValue = attr.nodeValue;
         if(nodeValue) {
            nodeValue = jaf.core.Util.escapeXml(nodeValue);
         }
         xmlString += " " + attName + "='" + nodeValue + "'";
      }

      if(!bDeep)  {
         xmlString += "/>";
         return xmlString;
      }else {
         if(nsURI === XHTMLNS || nsURI === ""  || (!nsURI))  {
            // optimizations for empty html tags
            if(emptyHtmlElements.indexOf(tagName) !== -1)  {
               xmlString += " />";
               return xmlString;
            }
         }
         xmlString += ">";
      }

      // children
      var childNodes = domElem.childNodes;
      for(i = 0, len = childNodes.length; i < len; i++)   {
         var child = childNodes[i];
         nodeType = child.nodeType;
         if(nodeType === 1) { // Element
            xmlString += _asXmlString(child, true);
         }else if(nodeType === 3) { // Text node
            xmlString += jaf.core.Util.escapeXml(child.nodeValue);
         }else if(nodeType === 4) { // CDATA Section
            xmlString += "<![CDATA[" + child.nodeValue + "]]>";
         }else if(nodeType === 5 || nodeType === 6) { // entity node
            xmlString += child.nodeName;
         }else if(nodeType === 8) { // Comment
            xmlString += "<!-- " + child.nodeValue + " -->";
         }
      }

      // end tag
      xmlString += "</" + tagName + ">";
      return xmlString;
   }

   function getElementsByAttributeNode(attrName, attrVal, optTagName, domElem)   {
      var elems = [];
      domElem = domElem || document;
      var children = domElem.getElementsByTagName(optTagName || "*");

      for(var i = 0, len = children.length; i < len; i++)   {
         var child = children[i];
         if(child.getAttribute(attrName) === attrVal)   {
            elems.push(child);
         }
      }
      return elems;
   }

   /** @scope jaf.core.Util */
   return   {
      /**
       * Trims the white spaces from the begining of the specified String
       * @param {String} s The String to trim
       * @return The "left trimmed" string
       */
      ltrim: function(s) {
         return s.replace(/(^\s+)/g, "");
      },

      /**
       * Trims the white spaces from the end of the specified String
       * @param {String} s The String to trim
       * @return The "right trimmed" string
       */
      rtrim: function(s) {
         return s.replace(/\s+$/g, "");
      },

      /**
       * Trims the white spaces from the begining and the end of the specified String
       * @param {String} s The String to trim
       * @return The "trimmed" string
       */
      trim: function(s) {
         return s.replace(/(^\s+)|\s+$/g, "");
      },

      /**
       * Gets whether the specified String <tt>s</tt> starts with the given String
       * <tt>strSearch</tt>
       * @param {String} s The string to search
       * @param {String} strSearch The search String
       * @return true if <tt>s</tt> starts with <tt>strSearch</tt>
       * @see endsWith(String, String)
       */
      startsWith: function(s, strSearch)  {
         return s.indexOf(strSearch) === 0;
      },

      /**
       * Gets whether the specified String <tt>s</tt> ends with the given String
       * <tt>strSearch</tt>
       * @param {String} s The string to search
       * @param {String} strSearch The search String
       * @return true if <tt>s</tt> ends with <tt>strSearch</tt>
       * @see startsWith(String, String)
       */
      endsWith: function(s, strSearch)   {
         if(s.indexOf(strSearch) === -1)   {
            return false;
         }
         return (s.lastIndexOf(strSearch) + strSearch.length) === s.length;
      },

      // from dojo
      escapeXml: function(s, /*boolean*/noSingleQuotes)  {
         // Adds escape sequences for special characters in XML: &<>"'
         // Optionally skips escapes for single quotes
         var str = s.replace(/&/gm, "&amp;")
               .replace(/</gm, "&lt;")
               .replace(/>/gm, "&gt;")
               .replace(/"/gm, "&quot;");
         if(!noSingleQuotes){
            str = str.replace(/'/gm, "&#39;");
         }
         return str; // string
      },

      toNumber: function(numVal, defaultVal)   {
         var tmpVal = Number(numVal);
         return isNaN(tmpVal) ? defaultVal ? this.toNumber(defaultVal) : 0 : numVal;
      },

      /**
       * Gets the string representation of the specified object. This method is
       * used for debugging
       * @param {Object} Object to convert to string
       * @return {String} The string representation of the object
       */
      toObjectSource: function(obj)   {
         if(obj === null)   {
            return "[null]";
         }
         if(obj === undefined) {
            return "[undefined]";
         }

         var str = "[";
         var member = null;
         for(var each in obj)	{
            member = obj[each];
            str += each + "=" + member + ", ";
         }
         return str + "]";
      },

      /**
       * Gets all the keys (properties) of the given object as an array
       * @param {Object} obj The object to retrieve keys
       * @param {Array} Properties of the object as an array
       */
      objectKeys: function(obj)  {
         var arr = [];
         for(var key in obj)   {
            if(this.hasOwnProperty(obj, key))   {
               arr.push(key);
            }
         }
         return arr;
      },

      /**
       * Gets a window wide unique id
       */
      nextUniqueId: function()   {
         var uid = 1;
         var win = window.opener ? window.opener.top : window.top;

         // this is because IE throws errors
         if(win.closed)  {
            win = window;
         }

         if(win.jaf_guid) {
            uid = ++win.jaf_guid;
         }else {
            win.jaf_guid = uid;
         }
         return "jaf_guid_" + uid;
      },

      hasOwnProperty: function(object, property)   {
         if(object.hasOwnProperty)  {
            return object.hasOwnProperty(property);
         }
         var prop = object[property];
         return typeof(prop) !== "undefined" && prop !== object.constructor.prototype[property];
      },

      /**
       * Gets the query parameters of the given URI as a associative array or map
       * e.g. Given a URI http://www.level3labs.com/jaf?foo=bar&baz=bazzm, will
       * give {"foo":"bar", "baz":"bazzm"}
       * @param {URI} strUri The uri with a query
       * @return Object containing query params or and empty object literal
       */
      getQueryParams: function(strUri)   {
         var paramMap = {};
         if(!strUri || strUri.length === 0)   {
            return paramMap;
         }

         var strQuery = strUri.substring(strUri.indexOf("?"));
         if(strQuery && strQuery.indexOf("?") === 0 && strQuery.length > 1) {
            strQuery = strQuery.substring(1);
            var paramValArr = strQuery.split("&");
            for(var i = 0, len = paramValArr.length; i < len; i++)   {
               var paramVal = paramValArr[i];
               var delimIdx = paramVal.indexOf("=");

               var param = null;
               var val = null;
               if(delimIdx >= 0) {
                  param = unescape(paramVal.substring(0, delimIdx));
                  val = unescape(paramVal.substring(delimIdx + 1));
               }else {
                  param = unescape(paramVal);
               }
               paramMap[param] = val;
            }
         }
         return paramMap;
      },

      getQuery: function(strUri) {
         var idx = strUri.indexOf("?");
         return idx === -1 ? null : strUri.substring(idx + 1);
      },

      /**
       * Sends an ajax request. All the details of the ajax request are specified in the options
       * @param {Object} options The options for this ajax request. This object literal
       * contains following properties and methods:<br />
       *
       * <pre style="color:#333;font-size:11px; font-family:'Lucida Console' 'Monospace';">
       * url         (String)     The url to make the ajax request  (window.location.href)
       * method      (String)     The HTTP method (GET|POST|HEAD)   ("GET")
       * contentType (String)     The content type of this request  ("application/x-www-form-urlencoded")
       * async       (boolean)    Whether to make an async request  (true)
       * data        (DOM|String) The data to send with request     (null)
       * dataType    (String)     The expected resultent dataType
       *                          ("xml"|"text"|"binary")           (null)
       * username    (String)     The username if required          (null)
       * password    (String)     The password if required          (null)
       * timeout     (Number)     The time in milliseconts to wait
       *                          for response                      (-1 indefinite)
       * headers     (Ass array)  Various headers as key:value      ({})
       *
       * onSuccess   (function)   The (optional) handler to be called with successfull
       *                          completion of request. The handler is passed 3
       *                          parameters:
       *                          onSuccess(responseXML, HTTP respnose code, HTTP response message) if
       *                          dataType is "xml" OR
       *                          onSuccess(responseText, HTTP respnose code, HTTP response message) if
       *                          if otherwise
       *
       * onError     (function)   The (optional) handler that is called when an error occurs during
       *                          ajax request. This is passed the same parameters as in onSuccess
       *                          handler
       * </pre>
       */
      ajax: function(options) {
         // copy the defaults that have not been overridden in options
         for(var prop in ajaxDefaults)  {
            if(typeof(options[prop]) === "undefined") {
               options[prop] = ajaxDefaults[prop];
            }
         }

         var xhr = (window.XMLHttpRequest) ? new XMLHttpRequest() :
               (window.ActiveXObject) ? new ActiveXObject("Microsoft.XMLHTTP")
               : null;

         if(!xhr)   {
            throw new Error("xhr.unsupported");
         }

         // open the connection socket
         // if a null username is passed, it pops up a dialog box in opera
         if(options.username) {
            xhr.open(options.method, options.url, options.async,
                  options.username, options.password);
         }else {
            xhr.open(options.method, options.url, options.async);
         }

         // set request headers
         var headers = options.headers;
         for(var headerKey in headers)  {
            xhr.setRequestHeader(headerKey, headers[headerKey]);
         }

         // set the content type
         if(options.data)  {
            xhr.setRequestHeader("Content-Type", options.contentType);
         }

         // set that this was request with AJAX
         xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

         xhr.onreadystatechange = function() {
            var rs = xhr.readyState;
            var dataType = options.dataType || "text";

            var empty = function()  {};
            var onSuccess = typeof (options.onSuccess) === "function" ?
                  options.onSuccess : empty;
            var onReceiving = typeof (options.onReceiving) === "function" ?
                  options.onReceiving : empty;
            var onError = typeof (options.onError) === "function" ?
                  options.onError : empty;

            switch(rs)  {
               case 2:
                  break;
               case 3:
                  if(onReceiving)   {
                     if(jaf.hostenv.isIE) {
                        // FIXME: IE cries if xhr.responseText is called here :(
                        return onReceiving("Receiving data", status);
                     }else {
                        return onReceiving(xhr.responseText, status);
                     }
                  }
                  break;
               case 4:
                  var code = xhr.status;
                  var message = xhr.statusText;

                  // check for HTTP errors
                  if(code >= 400 && onError) {
                     return onError(xhr.responseText, code, message);
                  }

                  if(dataType === "xml")   {
                     var doc = xhr.responseXML;
                     var root = doc.documentElement;
                     // check if there is a parse errro
                     if(root && root.nodeName === "parseerror" && onError)   {
                        return onError(xhr, code, "parseerror");
                     }else {
                        return onSuccess(xhr, code, "Success");
                     }
                  }else {
                     return onSuccess(xhr, code, "Success");
                  }
                  break;
               default:
                  break;
            }
            return null;
         };
         // send the request
         return xhr.send(options.data);
      },

      /**
       * Serializes the specified dom element to a string
       */
      asXmlString: function(domElem)   {
         if(! domElem)  {return null;}
         if(domElem.xml)   {
            return domElem.xml;
         }
         if(domElem.outerHTML)   {
            return domElem.outerHTML;
         }
         return _asXmlString(domElem, true);
      },

      /**
       * Gets the NS URI of the specified DOM element
       */
      getNsUri: function(domElem)   {
         if(domElem.namespaceURI)   {
            return domElem.namespaceURI;
         }else {
            // for IE
            var tagUrn = domElem.tagUrn || "";
            return tagUrn;
         }
      },

      /**
       * Creates a DOM element from the given string
       */
      asDom: function(xmlStr, contentType) {
         var type = contentType || "text/xml";
         var doc = null;
         var MOZ = (navigator.userAgent.indexOf("Gecko") !== -1);
         var IE =  (navigator.userAgent.indexOf("MSIE") !== -1);
         if(IE) {
            doc = new ActiveXObject("Microsoft.XMLDOM");
            doc.async = false;
            doc.loadXML(xmlStr);
         } else if(MOZ) {
            try {
               var parser = new DOMParser();
               doc = parser.parseFromString(xmlStr, type);
            } catch(e) {
               alert(e.message);
            }
         }
         return doc;
      },

      /**
       * creates xml document object with specified information
       * strQName - Qualified name of document root ele
       * strNsURI [optional] - the namespace URI of document
       * docType  [optional]-  doc type of document.
       */
      createXmlDocument: function(strQName, strNsURI, docType) {
         strNsURI = strNsURI || "";
         var domImpl = document.implementation;
         if(domImpl.createDocument) {
            return domImpl.createDocument(strNsURI, strQName, docType);
         }else {
            var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
            var node = xmlDoc.createNode(1, strQName, strNsURI);
            xmlDoc.documentElement = node;
            return xmlDoc;
         }
      },
      
      getAllResponseHeaders: function(xhr, strHeaderName) {
         var responseHeaders = xhr.getAllResponseHeaders().trim();
         var arr = responseHeaders.split("\r\n");
         var valueArr = [];
         
         for(var i = 0, len = arr.length; i < len; i++)  {
            var hdr = this.trim(arr[i]);
            arr[i] = hdr;
            if(strHeaderName && hdr.indexOf(strHeaderName) !== -1) {
               var value = hdr.substring(hdr.indexOf(":") + 1);
               valueArr.push(value);
            }
         }
         
         if(strHeaderName) {
            return valueArr;
         }
         return arr;
      },

      byId: function(strId) {
        return document.getElementById(strId);
      }
   };
}();


/**
 * A Channel/Topic/Queue to which messages are published. Each channel is represented
 * by a path name of the form /some/channel/name which is hierarchical
 * @constructor
 * @param {String} strName The name of this channel. Should be of the form /some/name
 */
jaf.core.Channel = function(strName)  {
   var name = strName;
   var subscribers = [];

   /** @scope jaf.core.Channel */
   return {
      /**
       * Adds a subscriber to this channel. All the messages published to this
       * channel will be delivered to it.
       * @param {Function} funSubscriber The subscriber function. This function takes one
       *        parameter, the message object
       */
      addSubscriber: function(funSubscriber) {
         if(typeof(funSubscriber) === "function")  {
            subscribers.push(funSubscriber);
         }
      },

      /**
       * Removes a previously added subscriber from this channel
       * @param {Function} funSubscriber The subscriber to remove
       */
      removeSubscriber: function(funSubscriber) {
         for(var i = 0, len = subscribers.length; i < len; i++)   {
            if(subscribers[i] === funSubscriber) {
               return subscribers.splice(i, 1);
            }
         }
         return null;
      },

      /**
       * Gets all subscribers as array
       * @return {Array} an array of subscribers added to this channel
       */
      getSubscribers: function()  {
         var len = subscribers.length;
         var copy  = new Array(len);
         for(var i = 0; i < len; i++)  {
            copy[i] = subscribers[i];
         }
         return copy;
      },

      /**
       * Publishes a messages to this channel. All the subscribers added to this
       * channel will receive this message
       * @param {Object} objMessage The message to publish
       */
      publish: function(objMessage) {
         for(var i = 0, len = subscribers.length; i < len; i++)   {
            subscribers[i](objMessage);
         }
      },

      getName: function()  {
         return name;
      },

      toString: function() {
         return "Channel[" + name + "]";
      }
   };
};


/**
 * Defines a simple publish/subscribe messaging system where messages can be sent
 * or subscribed for.
 * @constructor
 */
jaf.core.MessageBus = function(parentMessageBus)   {
   var channels = {};
   var patternSubscribers = {};
   var Util = jaf.core.Util;

   /**
    * IDEA: The parent child relationship will work as follows:
    * This message bus will subscribe to the parent message bus and listen to
    * all messages on all channels. As soon as messages come to those channels,
    * they will be published to channels with same path with this message bus
    * On the unload of this document or view, the listener to the parent message
    * bus is removed
    */
   var parent = parentMessageBus || null;

   /**
    * Creates a new Channel for the specified name. This method is always called
    * after calling checkChannelName(name)
    * @param {String} theName The name for this channel. This should be of the
    * form "/some/name"
    * @return {Channel} the newly created channel object
    */
   function createChannel(theName)   {
      // trim the trailing slash if any
      if(theName.length > 1 && Util.endsWith(theName, "/"))  {
         theName = theName.substring(0, theName.length - 1);
      }
      return new jaf.core.Channel(theName);
   }

   /**
    * Gets the segments of the specified channel name as array.
    * e.g. If the channel name is specified as "/some/name",  an array containing
    * two segments is returned like this: ["some", "name"]
    * @param {String} theName The channel name
    * @return {Array} The array containing the segments
    */
   function getSegments(theName) {
      var segs = theName.split("/");
      if(segs[0] === "")  {
         segs.slice(0, 1);
      }
      return segs;
      // return theName.split("/");
   }

   /**
    * Checks whether the specified channel name is a valid name
    * @param {String} theName The name of the channel
    */
   function checkChannelName(theName)   {
      if(!theName || Util.trim(theName).length === 0 || !Util.startsWith(theName, "/"))   {
         throw new Error("Invalid channel name: " + theName);
      }
   }

   /**
    * Determines if the specified name is a wildcard pattern
    * @return true if the name ends with /star or /starstar
    */
   function isPattern(theName)   {
      return Util.endsWith(theName, "/*") || Util.endsWith(theName, "/**");
   }

   /**
    * Determines if <tt>strName</tt> matches with channel name specified by
    * <tt>strChannel</tt>. This method also supports wildcard pattern matching
    * for two wildcards:<br />
    * "/star" For children and "/starstar" For descendents
    * @return true if strName matches with strChannel
    */
   function matches(strName, strChannel) {
      if(strName === strChannel) {
         return true;
      }

      if(!isPattern(strName) || isPattern(strChannel))  {
         return false;
      }

      // okay theName is a pattern
      var segs = getSegments(strName);
      var channelSegs = getSegments(strChannel);
      var depth = segs.length;
      var channelDepth = channelSegs.length;

      if(depth > channelDepth)   {
         return false;
      }

      // if(isPattern(strName)) {
      segs.splice(segs.length - 1, 1);
      depth = segs.length;

      var channelPart = (channelSegs.slice(0, depth).join("/"));
      var namePart = (segs.join("/"));
      if(channelPart !== namePart)  {
         return false;
      }

      if(Util.endsWith(strName, "/**"))   {
         return true;
      }else {
         return (channelDepth - depth) === 1;
      }
      // }
   }

   /**
    * Adds any of the subscribers that are pattern subscribers to a Channel.
    * This is called internally when a message is published to a channel and
    * that channel does not exist yet. When this happens, a new Channel is created
    * and any previously registered pattern subscribers are added to the channel
    * @param {jaf.core.Channel} objChannel The channel to which wildcard or pattern
    * subscribers are added if they match.
    */
   function addPatternSubscribers(objChannel)   {
      var patternChannels = Util.objectKeys(patternSubscribers);
      for(var i = 0, len = patternChannels.length; i < len; i++)  {
         var patternCh = patternChannels[i];
         if(matches(patternCh, objChannel.getName()))   {
            var subscribers = patternSubscribers[patternCh];
            if(subscribers)   {
               for(var j = 0, jlen = subscribers.length; j < jlen; j++)  {
                  objChannel.addSubscriber(subscribers[j]);
               }
            }
         }
      }
   }

   return {
      /**
       * Publishes the message <tt>objMessage</tt> to channel <tt>strChannel</tt>
       * All the subscribers registered with this channel will get the message
       * @param strChannel {String} The name of the channel to publish to
       * @param objMessage {Object} The message to publish
       */
      publish: function(strChannel, objMessage)  {
         if(isPattern(strChannel))  {
            // IDEA: Support publish to wildcard channels?
            return;
         }else {
            var channel = channels[strChannel];
            if(! channel)  {
               channel = createChannel(strChannel);
               channels[strChannel] = channel;
               addPatternSubscribers(channel);
            }
            channel.publish(objMessage);
         }
      },

      /**
       * Subscribes to specified channel. If the channel specified by <tt>strChannel</tt>
       * does not exist, its created. Any message published to this channel will
       * be delivered to <tt>funCallback</tt> who is the subscriber to this channel
       * @param strChannel {String} The name of the channel. This can be a wildcard
       * pattern
       * @param funCallback {Function} The callback function that will be called
       * with message as the parameter
       */
      subscribe: function(strChannel, funCallback)   {
         // check if the channel name is valid
         checkChannelName(strChannel);

         // check for valid callback function
         if(typeof(funCallback) !== "function")   {
            throw new Error("Invalid subsription callback. Must be a function");
         }

         var subscriber = funCallback;

         // check if this subscription is a pattern i.e. /some/name/** | some/name/*
         if(isPattern(strChannel))  {
            if(patternSubscribers[strChannel])  {
               patternSubscribers[strChannel].push(subscriber);
            }else {
               patternSubscribers[strChannel] = [subscriber];
            }

            // check if this subscriber matches for any of the channel already created
            var channelNames = Util.objectKeys(channels);
            for(var i = 0, len = channelNames.length; i < len; i++)  {
               var cName = channelNames[i];
               if(matches(strChannel, cName)) {
                  channels[cName].addSubscriber(subscriber);
               }
            }
         }else {  //no pattern so existing or a new channel
            var channel = channels[strChannel];
            if(! channel)  {
               channel = createChannel(strChannel);
               channels[strChannel] = channel;
            }
            channel.addSubscriber(subscriber);
         }
      },

      /**
       * Unsubscribes to specified channel.
       * @param strChannel {String} The name of the channel (can be a wildcard
       * in which case funCallback from all the matching channels is removed
       * @param funCallback {Function} The callback function that was used when
       * subscribing
       */
      unsubscribe: function(strChannel, funCallback) {
         var subscriber = null;
         if(isPattern(strChannel))  {
            var arrSubs = patternSubscribers[strChannel];
            var subsIndex = -1;
            if(arrSubs) {
               for(var i = 0, len = arrSubs.length; i < len; i++) {
                  if(arrSubs[i] === funCallback) {
                     subsIndex = i;
                     break;
                  }
               }

               if(subsIndex !== -1)  {
                  arrSubs.splice(subsIndex, 1);
               }
            }
         }

         // check if this subscriber matches for any of the channel already created
         var channelNames = Util.objectKeys(channels);
         for(var j = 0, jlen = channelNames.length; j < jlen; j++)  {
            var cName = channelNames[j];
            if(matches(cName, strChannel)) {
               subscriber = channels[cName].removeSubscriber(subscriber);
            }
         }
      },

      toString: function() {
         return "MessageBus"
      }
   };
}(null);


/**
 * Loads javascript files or CSS files into the document
 */
jaf.core.Loader = function() {
   var Util = jaf.core.Util;
   /**
    * Loads the specified library into the current document. Supported are
    * CSS and javascript files
    * @param {String} strUrl The URL of the script or CSS file
    * @param {String} strType The mime type of the library
    * @param {Document|Element} domElem The document in which the library is to be loaded
    * @param {Function} funCallback The function to call on successfull load
    */
   function loadLibrary(strUrl, strType, funCallback, domElem)  {
      var theElem = domElem || document;
      // if this library is already loaded just call the function
      if(isLibraryLoaded(strUrl, theElem))   {
         if(funCallback)   {
            funCallback();
         }
         return;
      }

      var isDoc = theElem.nodeType === 9;
      var libElem = createLibraryElement(strUrl, strType, (isDoc ? theElem : document));
      var hd = isDoc ? theElem.getElementsByTagName("head")[0] : theElem;

      if(libElem && hd) {
         if(strType === "text/javascript" || strType === "application/javascript" ||
               strType === "application/x-javascript")   {
            // attach onload handlers
            if(jaf.hostenv.isIE)   {
               jaf.poll(function cond()  {
                  return (libElem.readyState === "loaded" ||  libElem.readyState === "complete");
               }, 10, funCallback);
            }else {
               // for mozilla, firefox -:)
               libElem.addEventListener("load", funCallback, false);
            }
         }else {
            funCallback();
         }
         hd.appendChild(libElem);

         var libs = theElem.libraries;
         if(typeof(libs) === "undefined") {
            libs = theElem.libraries = {};
         }
         libs[strUrl] = true;
      }// if(libElem...
   }

   /**
    * Determines if the library by path <tt>strUrl</tt> is successfully loaded
    * in the given document
    * @param {String} strPath The path of the library
    * @param {Document} doc The document obejct
    * @return true if the given library has already been loaded in the document
    */
   function isLibraryLoaded(strPath, doc)  {
      doc = doc || document;
      var libs = doc.libraries;
      if(typeof(libs) === "undefined") {
         return false;
      }
      return libs[strPath] === true;
   }

   /**
    * Creates a "<script>" or a "<link>" element depending the type of
    * export.
    * @param {String} strUrl The url of the export file
    * @param {String} type The type of export. Currently supported are "text/javascirpt"
    * "text/css"
    * @param {Document} doc The DOM document in which to create the library element
    * @return the dom element representing the export
    */
   function createLibraryElement(strUrl, type, doc) {
      var libElem = null;
      if(type === "text/javascript" || type === "application/x-javascript")  {
         libElem = doc.createElement("script");
         libElem.setAttribute("type", type);
         if(strUrl) {
            libElem.setAttribute("src", strUrl);
            libElem.src = strUrl;
         }
      }else if(type === "text/css")  {
         libElem = doc.createElement("link");
         libElem.setAttribute("rel", "stylesheet");
         libElem.setAttribute("type", type);
         if(strUrl) {
            libElem.setAttribute("href", strUrl);
            libElem.href = strUrl;
         }
      }

      if(libElem) {
         libElem.setAttribute("id", strUrl.replace("/", "_", "g"));
      }
      return libElem;
   }
   
   function doCallback(arrsInfo, fnCallback)   {
      try   {
         fnCallback();
      }catch(err) {
         var s = "";
         for(var k = 0, klen = arrsInfo.length; k < klen; k++)  {
            s += arrsInfo[k].url + "\n";
         }
         alert(Util.toObjectSource(err) + ": In call-back function while loading:\n\n" + s);
      }
   }

   return {
      /**
       * Loads a javascript file/resource into the specified domElem
       * @see loadFile(strUrl, strType, domDoc, funCallback)
       */
      loadJs: function(strUrl, funCallback, domElem)   {
         loadLibrary(strUrl, "text/javascript", funCallback, domElem);
      },

      /**
       * Loads the specified CSS stylesheet
       * @param {String} strUrl The url of the CSS stylesheet
       * @param {Document|Element} domElem The document or Element into which to load the CSS
       */
      loadCss: function(strUrl, domElem)   {
         loadLibrary(strUrl, "text/css", null, domElem);
      },

      /**
       * Loads a javascript or CSS into the specified document.
       * @param {String} strUrl The URL of the js file
       * @param {String} strType The type of the file (text/css OR text/javascript OR
       * application/x-javascript)
       * @param {Document|Element} domElem The document into which to load the JS file
       * @param {Function} funCallback The function to call after successfull
       * loading of the file
       */
      loadFile: function(strUrl, strType, funCallback, domElem) {
         return loadLibrary(strUrl, strType, funCallback, domElem);
      },

      /**
       * Loads an array of scripts into the specified dom document and calls
       * the given function after successfully loading the scripts
       * @param arrScriptInfo The array of script info objects. The objects have
       * following fields {url:"<someurl>", type:"text/css | text/javacript"}
       * @param domElem The DOM element (or Document) into which to load the script
       */
      load: function(arrScriptInfo, funCallback, domElem) {
         var idx = 0;
         // var loader = this;

         var loadCallback = function()   {
            ++idx;
            if(idx >= arrScriptInfo.length) {
               doCallback(arrScriptInfo, funCallback);
            }else {
               loadInternal();
            }
         };

         var loadInternal = function() {
            if(arrScriptInfo.length > 0)  {
               var sInfo = arrScriptInfo[idx];
               loadLibrary(sInfo.url, sInfo.type, loadCallback, domElem);
            }else {
               doCallback(arrScriptInfo, funCallback);
            }
         };

         loadInternal();
      },

      toString: function() {
         return "jaf.core.Loader";
      }
   };
}();


jaf.core.Runtime = function(domDoc)   {
   var id = "jaf:runtime";
   var doc = domDoc || document;
   var modules = {};
   var moduleIdUrlMap = {};
   var importedMods = {};

   var bus = jaf.core.MessageBus;
   var loader = jaf.core.Loader;
   var Util = jaf.core.Util;

   return   {
      /**
       * Gets all the ids of available modules with this Runtime
       * @return {String[]} an array of all the module ids
       */
      getModuleIds: function()   {
         var modIds = [];
         for(var modId in modules)  {
            if(Util.hasOwnProperty(modId, modules)) {
               modIds.push(modId);
            }
         }
         return modIds;
      },

      /**
       * Gets a module with the specified module id or null if the module
       * was not found.
       * @param {String} modId The module id of the module to obtain
       */
      getModule: function(modId)   {
         return modules[modId];
      },

      /**
       * Loads the module META information into this runtime
       * @param {String} modPath The path of the module. This is the directory
       * of the module that contains META-INF/jaf-module.js descriptor
       * @param {Function} successHandler The handler function that will be called
       * when module successfully loads. The handler function is passed one argument,
       * the module object (described by module.js) that was currently loaded.
       * @param {Function} errorHandler The handler function that will be called
       * if there is an error loading the module. The error handler is passed
       * two arguments, the module id or modulePath and the error that occured
       */
      loadModule: function(modPath, successHandler, errorHandler)   {
         if(!modPath)   {
            throw new Error("modurl.notspecified");
         }
         var module = null;
         var modId = moduleIdUrlMap[modPath];

         successHandler = typeof(successHandler) === "function" ? successHandler : null;
         errorHandler = typeof(errorHandler) === "function" ? errorHandler : null;

         try   {
            module = modules[modId];
            if(module)  {
               if(successHandler)  {
                  successHandler(module);
               }
               return;
            }

            var modConfigUrl = modPath + "/META-INF/jaf-module.js";

            // get the config file
            Util.ajax({
               url: modConfigUrl,
               dataType: "text",
               onSuccess: function(xhr, code, message)   {
                  var responseData = xhr.responseText;
                  try   {
                     var tmp = new Function("return " + responseData);
                     module = tmp();
                     modId = module.id || Util.nextUniqueId();
                     moduleIdUrlMap[modPath] = modId;
                     module.baseUrl = modPath;
                     modules[modId] = module;
                     if(! Util.hasOwnProperty(module, "toString"))  {
                        module.toString = function()  {
                           return "Module[" + modId + "]";
                        };
                     }
                     bus.publish("/jaf/core/Runtime/ModuleLoad", module);

                     if(successHandler)   {
                        successHandler(module);
                     }
                  }catch(err) {
                     if(errorHandler)  {
                        errorHandler(modId || modPath, err);
                     }
                  }
               },
               onError: function(xhr, code, msg)   {
                  var httpErr = {name:code, message:msg};
                  if(errorHandler)  {
                     errorHandler(modId || modPath, httpErr);
                  }
               }
            });
         }catch(err) {
            if(errorHandler)  {
               errorHandler(modId || modPath, err);
            }
         }
      },

      /**
       * Loads an array of modules into this runtime. This is similar to load
       * module but is called sequentially called for each module in the array
       * The next call to loadModule for the module in the specified array will
       * only happen after the previous module was loaded
       * @see #loadModule(String, Function, Function)
       */
      loadModules: function(arrMods, successHandler, errorHandler)   {
         var idx = 0;
         var loader = this;
         var arrNotLoaded = [];

         /**
          * This increments the index calls the loadInternal again to load the
          * next module, which in turn on success will call this function and
          * so on until all the modules in the sepcified module array are loaded
          * When that happens it calls the successHandler
          */
         var loadCallback = function()   {
            ++idx;
            if(idx >= arrMods.length) {
               if(arrNotLoaded.length > 0)   {
                  errorHandler(arrNotLoaded);
               }else {
                  successHandler();
               }
            }else {
               loadInternal();
            }
         };

         /**
          * Used internally to handle module load errors. This function accumelates
          * a local variable with unloaded modules along with a reason as to why
          * there were not loaded. Finally after iterating through all the modules
          * calls errorHandler if there were any errors loading any of the modules
          */
         var loadErrorCallback = function(modIdOrPath, err)  {
            ++idx;
            arrNotLoaded.push(modIdOrPath + ": " + err);
            if(idx >= arrMods.length) {
               errorHandler(arrNotLoaded);
            }else {
               loadInternal();
            }
         };

         /**
          * Actually imports each module
          */
         var loadInternal = function() {
            if(arrMods.length > 0)  {
               var mInfo = arrMods[idx];
               loader.loadModule(mInfo, loadCallback, loadErrorCallback);
            }else {
               successHandler();
            }
         };

         loadInternal();
      },

      /**
       * "Imports" a module into the current document. This means that all the
       * libraries that the module exports are imported into the current document
       * @param {String} modId The module id of the module to import
       * @param {Function} successHandler Called when all the module is "imported"
       * @param {Function} errorHandler Called when there is an error importing the module
       */
      importModule: function(modId, successHandler, errorHandler)   {
         var mod = importedMods[modId];
         if(mod)  {
            successHandler();
            return;
         }
         mod = this.getModule(modId);
         if(mod)  {
            var exportInfos = mod.exports;
            // resolve the url of all exports
            for(var i = 0, len = exportInfos.length; i < len; i++)   {
               var exportInfo = exportInfos[i];
               if(! Util.startsWith(exportInfo.url, "/"))  {
                  exportInfo.url = mod.baseUrl + "/" + exportInfo.url;
               }
            }

            loader.load(exportInfos, function()   {
               if(typeof(successHandler) === "function") {
                  successHandler(mod);
               }
            });
         }else {
            errorHandler(modId, {name:"ModuleImport", message:"Module not loaded"});
         }
      },

      toString: function()   {
         return id;
      }
   };
}();
